package cn.net.xyan.easy.webapp.base.service.impl;

import cn.net.xyan.easy.spring.core.exception.StatusAndMessageError;
import cn.net.xyan.easy.webapp.base.dao.PermissionDao;
import cn.net.xyan.easy.webapp.base.dao.PermissionGroupDao;
import cn.net.xyan.easy.webapp.base.dao.SystemUserDao;
import cn.net.xyan.easy.webapp.base.api.dto.*;
import cn.net.xyan.easy.webapp.base.entity.Permission;
import cn.net.xyan.easy.webapp.base.entity.PermissionGroup;
import cn.net.xyan.easy.webapp.base.entity.SystemUser;
import cn.net.xyan.easy.webapp.base.entity.status.PermissionGroupStatus;
import cn.net.xyan.easy.webapp.base.entity.status.UserStatus;
import cn.net.xyan.easy.webapp.base.service.ISecurityService;
import cn.net.xyan.easy.webapp.base.service.ISystemConfigService;
import cn.net.xyan.easy.webapp.base.support.SecurityUserDetails;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

/**
 * Created by xiashenpin on 16/1/24.
 */
public class JPASecurityService implements ISecurityService  {

    @Autowired
    SystemUserDao userDao;

    @Autowired
    PermissionDao permissionDao;

    @Autowired
    PermissionGroupDao groupDao;

    @Autowired
    ISystemConfigService configService;

    @PostConstruct
    public void setup(){

        configService.setupStatus(UserStatus.class,ISecurityService.ActiveStatus,ISecurityService.ActiveStatusTitle);
        configService.setupStatus(UserStatus.class,ISecurityService.InActiveStatus,ISecurityService.InActiveStatusTitle,true);

        configService.setupStatus(PermissionGroupStatus.class,ISecurityService.ActiveStatus,ISecurityService.ActiveStatusTitle);
        configService.setupStatus(PermissionGroupStatus.class,ISecurityService.InActiveStatus,ISecurityService.InActiveStatusTitle,true);

        setupPermission(USERPermission,USERPermission);
        Permission superPermission = setupPermission(SUPERPermission,SUPERPermission);

        setupUser(SUPERUSER,SUPERUSER,superPermission);

    }

    protected void updatePermissions(Integer command,String permissionName,List<Permission> permissions){
        Permission permission = permissionDao.findByPermission(permissionName);
        if (permission== null)
            return;
        if (ISecurityService.Add == command){
            permissions.add(permission);
        }else if(ISecurityService.Remove == command){
            permissions.remove(permission);
        }
    }

    @Override
    public Boolean isUserNameExist(String username) {
        return userDao.exists(username);
    }

    @Override
    public SystemUser queryUserByUsername(String username) {
        return userDao.findOne(username);
    }

    @Override
    @Transactional
    public SystemUser registerUser(CommitRegisterUserReq req) {
        if (isUserNameExist(req.getUsername())){
            throw new StatusAndMessageError(-1,"user:"+req.getUsername() +" is already exist!");
        }
        SystemUser user = new SystemUser(req.getUsername(),req.getPassword());
        user.setEmail(req.getEmail());
        user.setRealName(req.getRealName());
        UserStatus status = configService.readStatus(UserStatus.class,ISecurityService.ActiveStatus);
        user.setStatus(status);
        return userDao.saveAndFlush(user);
    }

    @Override
    public SystemUser updateUser(CommitUpdateUserReq req) {
        if (!isUserNameExist(req.getUsername())){
            throw new StatusAndMessageError(-1,"user:"+req.getUsername() +" is not exist!");
        }
        SystemUser user = userDao.findOne(req.getUsername());
        if (req.getNewPassword()!=null && req.getNewPassword().length()>0 ){
            if (req.getOldPassword() == null|| req.getOldPassword().length()== 0 || !req.getOldPassword().equals(user.getPassword())){
                throw new StatusAndMessageError(-1,"please set correct password!");
            }
            user.setPassword(req.getNewPassword());
        }
        if (req.getNewEmail()!=null)
            user.setEmail(req.getNewEmail());
        if (req.getNewRealName()!=null)
            user.setRealName(req.getNewRealName());

        if (req.getStatus()!=null){
            UserStatus status = configService.readStatus(UserStatus.class,req.getStatus());
            if (status!=null){
                user.setStatus(status);
            }
        }
        return userDao.saveAndFlush(user);
    }

    @Override
    public SystemUser updateUserPermission(CommitUpdatePermissionForUserReq req) {
        if (!isUserNameExist(req.getUsername())){
            throw new StatusAndMessageError(-1,"user:"+req.getUsername() +" is not exist!");
        }
        SystemUser user = userDao.findOne(req.getUsername());

        List<String> permissions = req.getPermissions();
        if (permissions!=null){
            for (String permissionValue : permissions){
                updatePermissions(req.getCommand(),permissionValue,user.getPermissions());
            }
        }

        List<String> groups = req.getGroups();
        if (groups!=null){
            for (String groupName:groups){
                PermissionGroup group = groupDao.findByGroupName(groupName);
                if (group== null)
                    continue;
                if (req.getCommand() == CommitUpdatePermissionForUserReq.Add){
                    user.getPermissionGroups().add(group);
                }else if(req.getCommand() == CommitUpdatePermissionForUserReq.Remove){
                    user.getPermissionGroups().remove(group);
                }
            }
        }

        return userDao.saveAndFlush(user);
    }

    @Override
    public SystemUser setupUser(String username, String password, Permission permission) {
        SystemUser user = userDao.findOne(username);
        if (user == null){
            user = new SystemUser(username,password);
            userDao.saveAndFlush(user);
            user.getPermissions().add(permission);
            UserStatus status = configService.readStatus(UserStatus.class,ISecurityService.ActiveStatus);
            user.setStatus(status);
            user = userDao.saveAndFlush(user);
        }

        return user;
    }

    @Override
    public Page<SystemUser> allUsers(org.springframework.data.domain.Pageable pageable) {
        return userDao.findAll(pageable);
    }

    @Override
    public SystemUser currentUser() {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        String name = auth.getName(); //get logged in username
        return userDao.findOne(name);
    }

    @Override
    public PermissionGroup createPermissionGroup(CommitCreatePermissionGroupReq req) {
        PermissionGroup pg = new PermissionGroup(req.getGroupName());

        PermissionGroupStatus status = configService.readStatus(PermissionGroupStatus.class,ISecurityService.ActiveStatus);
        pg.setStatus(status);
        pg.setDescribe(req.getDescribe());

        List<String> permissions = req.getPermissions();
        if (permissions!=null){
            for (String permissionValue : permissions) {
                Permission permission = permissionDao.findByPermission(permissionValue);
                if (permission == null)
                    continue;
                pg.getPermissions().add(permission);
            }
        }

        return groupDao.saveAndFlush(pg);
    }

    @Override
    public PermissionGroup updatePermissionGroup(CommitUpdatePermissionGroupReq req) {
        PermissionGroup group = queryPermissionGroupByGroupName(req.getGroupName());
        if (group == null){
            throw new StatusAndMessageError(-1,"group:"+req.getGroupName() +" is not exist!");
        }
        List<String> permissions = req.getPermissions();
        if (permissions!=null){
            for (String pName : permissions){
                updatePermissions(req.getCommand(),pName,group.getPermissions());
            }
        }
        return groupDao.saveAndFlush(group);
    }

    @Override
    public PermissionGroup queryPermissionGroupByGroupName(String groupName) {
        return groupDao.findByGroupName(groupName);
    }

    @Override
    public Permission setupPermission(String permission, String describe) {
        Permission p = permissionDao.findByPermission(permission);
        if (p == null){
            p = new Permission(permission);
            p.setDescribe(describe);
            p=permissionDao.saveAndFlush(p);
        }
        return p;
    }

    @Override
    public Boolean checkPermissionForUser(SystemUser user, Permission... permissions) {
        return checkPermissionForUser(user,Arrays.asList(permissions));
    }

    @Override
    public Boolean checkPermissionForUser(SystemUser user, List<Permission> permissions) {
        Permission superPermission = permissionDao.findByPermission(SUPERPermission);
        if (permissionDao.checkUserHasPermission(user,superPermission)!=null){
            return true;
        }
        List<Permission> rsp = permissionDao.checkUserHasPermissions(user,permissions);
        return permissions.size() == rsp.size();
    }

    @Override
    public List<Permission> queryPermissionForUser(SystemUser user) {
        Permission superPermission = permissionDao.findByPermission(SUPERPermission);
        if (superPermission !=null && permissionDao.checkUserHasPermission(user,superPermission)!= null){
            return permissionDao.findAll();
        }else{
            return permissionDao.findAllPermissionForUser(user);
        }
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        UserDetails userDetails = null;
        SystemUser user = userDao.findOne(username);
        Collection<GrantedAuthority> grantedAuths = new ArrayList<GrantedAuthority>();
        if (user == null){
            user = new SystemUser();
            user.setUsername(username);
            user.setPassword("");
            grantedAuths = AuthorityUtils.NO_AUTHORITIES;
            userDetails = new SecurityUserDetails(user,
                    false, false, false, false,
                    grantedAuths);
        }
        else{

            boolean accountNonLocked = !user.getStatus().getAbandon();

            for(Permission permission : queryPermissionForUser(user)){
                grantedAuths.add(new SimpleGrantedAuthority(String.format("ROLE_%s",permission.getPermission())));
            }
            userDetails = new SecurityUserDetails(user,
                    true, true, true, accountNonLocked,
                    grantedAuths);
        }

        return userDetails;
    }
}
